batchTips

Must Watch!



MustWatch


PowerShell's great. I'm fired up about the opportunity to use .NET objects from simple scripts. I'll admit I'm still getting up to speed with it, but I'm totally sold on PowerShell. However, it's not installed on a lot of servers I work with, and I still do a lot of my "clumsy developer attempting DBA and network admin" tasks from DOS Batch files. You can do quite a bit with DOS Batch - the silly fact is the my most popular posts (by a huge margin) are some batch scripts to allow running IE7 and IE6 on the same computer. So, by way of tribute to the dying art of the DOS Batch file, I present my top ten batch file tricks:

Use PUSHD / POPD to change directories

Read Scott Hanselman's writeup on PUSHD. The basic idea is that it keeps a stack, so at the simplest level you can do something like this: PUSHD "C:\Working Directory\" ::DO SOME WORK POPD That allows you to call the batch file from any directory and return to the original directory when you're done. The cool thing is that PUSHD can be nested, so you can move all over the place within your scripts and just POPD your way out when you're done.

Call FTP scripts

This sample prompts for the username and password, but they can of course be hardcoded if you're feeling lucky. set FTPADDRESS=ftp.myserver.com set SITEBACKUPFILE=FileToTransfer.zip set /p FTPUSERNAME=Enter FTP User Name: set /p FTPPASSWORD=Enter FTP Password: CLS > script.ftp USER >script.ftp ECHO %FTPUSERNAME% >script.ftp ECHO %FTPPASSWORD% >script.ftp ECHO binary >script.ftp ECHO prompt n :: Use put instead of get to upload the file >script.ftp ECHO get %SITEBACKUPFILE% >script.ftp ECHO bye FTP -v -s:script.ftp %FTPADDRESS% TYPE NUL >script.ftp DEL script.ftp

Read from the registry

You can make creative use of the FOR command to read from and parse a registry value (see my previous post for more info). FOR /F "tokens=2* delims= " %%A IN ('REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" /v SQL2005') DO SET SQLINSTANCE=%%B

Run SQL Commands

You can call OSQL (or SQLCMD on servers with SQL 2005 installed) to execute SQL commands: osql -E -d master -Q "BACKUP DATABASE [%DATABASENAME%] TO DISK = N'D:\DataBase\Backups\%DATABASENAME%_backup' WITH INIT , NOUNLOAD , NAME = N'%DATABASENAME% backup', NOSKIP , STATS = 10, NOFORMAT"

Check if a file or folder exists

I used this to do a quick and dirty check to see if a Windows Hotfix had been installed in my IE7 Standalone scripts: IF EXIST %SystemRoot%\$NtUninstallKB915865$\ GOTO KB_INSTALLED ECHO Installing Hotfix (KB915865) to allow tab support START /D "%~dp0/Installation/Update/" xmllitesetup.exe

Pause execution for a number of seconds

There are different ways to do this from within a batch file, all with their tradeoffs. I use a ping to an invalid IP address with a timeout. The best way to do this is to find an invalid IP address and then pint it, but 1.1.1.1 is a pretty safe bet: ECHO Waiting 15 seconds PING 1.1.1.1 -n 1 -w 15000 > NUL

Use defaults for optional parameters

It's not really easy to check for a missing parameter. You have to use something like "IF dummy==%1dummy", which will only be true if %1 is empty. So, for example, here we're allowing a user to supply an application path via the third parameter, and defaulting it if it's missing. By the way, beware the IF syntax. The line spacing makes a difference, so this is one that I just copy and paste to avoid figuring it out every time. IF dummy==dummy%3 ( SET APPLICATIONPATH="C:\Program Files\MyApp\" ) ELSE ( SET APPLICATIONPATH = %3 )

Process each file matching a pattern in a directory

I previously posted a script which iterates all files named *.bak in a directory and restores them on the local instance of SQL Server. Here's an excerpt: PUSHD %BACKUPDIRECTORY% FOR %%A in (*.bak) do CALL :Subroutine %%A POPD GOTO:EOF :Subroutine set DBNAME=%~n1 ::RUN SOME OSQL COMMANDS TO RESTORE THE BACKUP GOTO:EOF

Use batch parameter expansion to avoid parsing file or directory info

Batch file parameters are read as %1, %2, etc. DOS Command Extensions - available on Windows 2000 and up - add a lot of automatic parsing and expansion that really simplifies reading filenames passed in as parameters. I originally put this at the top of the list, but I moved it because I figured the insane syntax would drive people off. I wrote a simple batch script that shows some examples. I think that makes it a little more readable. Stick with me, I think this is one of the best features in DOS batch and is worth learning. First, here's the batch file which just echos the processed parameters: @echo off echo %%~1 = %~1 echo %%~f1 = %~f1 echo %%~d1 = %~d1 echo %%~p1 = %~p1 echo %%~n1 = %~n1 echo %%~x1 = %~x1 echo %%~s1 = %~s1 echo %%~a1 = %~a1 echo %%~t1 = %~t1 echo %%~z1 = %~z1 echo %%~$PATHATH:1 = %~$PATHATH:1 echo %%~dp1 = %~dp1 echo %%~nx1 = %~nx1 echo %%~dp$PATH:1 = %~dp$PATH:1 echo %%~ftza1 = %~ftza1 Now we'll call it, passing in "C:\Windows\Notepad.exe" as a parameter: C:\Temp>batchparams.bat c:\windows\notepad.exe %~1 = c:\windows\notepad.exe %~f1 = c:\WINDOWS\NOTEPAD.EXE %~d1 = c: %~p1 = \WINDOWS\ %~n1 = NOTEPAD %~x1 = .EXE %~s1 = c:\WINDOWS\NOTEPAD.EXE %~a1 = --a------ %~t1 = 08/25/2005 01:50 AM %~z1 = 17920 %~$PATHATH:1 = %~dp1 = c:\WINDOWS\ %~nx1 = NOTEPAD.EXE %~dp$PATH:1 = c:\WINDOWS\ %~ftza1 = --a------ 08/25/2005 01:50 AM 17920 c:\WINDOWS\NOTEPAD.EXE As I said, the syntax is completely crazy, but it's easy to look them up - just type HELP CALL at a DOS prompt; it gives you this: %~1 - expands %1 removing any surrounding quotes (") %~f1 - expands %1 to a fully qualified path name %~d1 - expands %1 to a drive letter only %~p1 - expands %1 to a path only %~n1 - expands %1 to a file name only %~x1 - expands %1 to a file extension only %~s1 - expanded path contains short names only %~a1 - expands %1 to file attributes %~t1 - expands %1 to date/time of file %~z1 - expands %1 to size of file %~$PATH:1 - searches the directories listed in the PATH environment variable and expands %1 to the fully qualified name of the first one found. If the environment variable name is not defined or the file is not found by the search, then this modifier expands to the empty string The modifiers can be combined to get compound results: %~dp1 - expands %1 to a drive letter and path only %~nx1 - expands %1 to a file name and extension only %~dp$PATH:1 - searches the directories listed in the PATH environment variable for %1 and expands to the drive letter and path of the first one found. %~ftza1 - expands %1 to a DIR like output line In the above examples %1 and PATH can be replaced by other valid values. The %~ syntax is terminated by a valid argument number. The %~ modifiers may not be used with %*

Learn from the masters

By far, my favorite resource for DOS Batch trickery is the Batch Files section of Rob van der Woude's Scripting Pages. He's got some good PowerShell resources, too. What about you? Got any favorite DOS Batch tricks? Batch files have been around since the early Windows operating system. These are plain text files with .bat|.cmd|.btm file extensions, and when executed, the commands are interpreted by the Windows command-line interface. While Microsoft released a more advanced command-line tool called PowerShell (this is not installed by default), the native windows command line remains a popular choice for scripting. Here are some tips and tricks for developers and non-technical people alike: Displaying of Comments or Remarks (ECHO and REM) Documentation is important, especially for very long batch files. However, during execution, these could clutter the display and can be difficult to read. Using ‘@REM’ and ‘@ECHO OFF There are two differences between ECHO and REM. REM can totally be hidden during execution, while ECHO can at some level be suppressed, but will always be displayed during execution. Here are some tricks on how to change displays of comments and remarks by prepending with ‘@’ character: @REM will hide the remark entirely and will not be shown on the command-line. All REM lines after an @ECHO OFF call will also be hidden. These are suitable for documentation intended for developers writing the script. ECHO by default will be displayed during command invocation, as well as on the standard output. Using @ECHO or if preceded by @ECHO OFF will display the command on the standard output only. This is suitable for creating markers during execution to show at which point the command has executed so far. Consider the following batch file: ECHO "ECHO displayed on the command line and on the standard output." @ECHO "ECHO is not displayed on the command line but will be displayed on standard output." REM "REM displayed on the command line." @REM "REM hidden from the command line." @ECHO OFF ECHO "all ECHO after @ECHO OFF will be shown on the standard output, but not on the command line invocation." REM "REM hidden from the command line after @ECHO OFF" Its corresponding output is: Batch File Tips and Tricks @REM and @ECHO tips. Error Handling In its simplest form, batch files are straightforward in its sequential command execution. However, there are tasks that need error handling, either for notification or cleanup. Batch files, after executing a command, provides an error code called ERRORLEVEL. A non-zero value means an error has occured. ERRORLEVEL vs %ERRORLEVEL% Both pertain to the current error code, and has the same value. The main difference between the two is that %ERRORLEVEL% is handled just like a variable, while ERRORLEVEL is handled specifically by the if operation. So if you would like to check for errors: if ERRORLEVEL 1 goto :ERROR if %ERRORLEVEL% neq 0 goto :ERROR The abovementioned snippets would have the same behavior. ERRORLEVEL checks if the value is equal to or greater than the number specified. Example error handling in Windows batch files: @ECHO OFF ECHO "Force Error" ping -invalid-arg if ERRORLEVEL 1 goto ERROR REM /B to avoid the command line window to close EXIT /B %errorlevel% :ERROR ECHO "An ERROR has occurred." ECHO "Proceed with error handling or cleanup." The output of this would be: PushMon - Batch File Tips and Tricks Windows Batch File Error Handling Example Use VERIFY as a simple check for copying or moving files. Do you copy files within your batch file? It is worth it to add a one liner command VERIFY ON, to perform a simple check of the destination file. The operation is not comprehensive, as it does not check if the file is corrupted. However, if you are transferring a lot of files (e.g. backups, migration), then this is a first step to ensure integrity of the operation. You can use xcopy or copy as copy operations: @ECHO OFF REM copy command with /v option; verify is a simple read operation copy /v PushMon.bat copy_PushMon.bat REM xcopy command /v option; a more advanced version which checks for the file size of the copied files xcopy /v PushMon.bat xcopy_PushMon.bat VERIFY ON REM copy command; verify is a simple read operation copy PushMon.bat copy2_PushMon.bat REM xcopy command; a more advanced version which checks for the file size of the copied files copy /v PushMon.bat xcopy2_PushMon.bat if ERRORLEVEL 1 goto ERROR EXIT %errorlevel% :ERROR ECHO "Copying of files have failed." Do you have other tips and tricks for creating Windows Batch Files? Comment on this page and will add it up to the list. PushMon supports the use of Windows batch files. If you have batch files that are scheduled to execute at specific intervals, you can use PushMon to monitor its execution and notify you if something goes wrong. We have created a separate blog post on how to monitor your batch files using Windows task scheduler.

Trick #1 - Taking user input and doing arithmetic operations

Often times, it is very important to write interactive scripts that take input from the user and carry arithmetic operations if needed. To read user input, we can use the following syntax: Copy Code SET /P VarName=The prompt text So for example, to ask the user for his/her name and display it back again, we can do the following: Copy Code @echo off setlocal SET /P Name="What is your name? " echo Welcome %Name% Quote: It is important not to pollute the environment variables namespace when using the "SET" command and therefore it is a good practice to start your script with the "setlocal" command that will revert the environment variable changes when the script terminates. Similarily, to do arithmetic operations, we can use the following syntax: Copy Code SET /A Number=Expression For example: Copy Code SET /A Number=1+2+3+4 Would compute into the variable "Number" the value "10". Now, we can combine those two tricks to create a small integer expression calculator: Copy Code @echo off setlocal :repeat SET /P Expr="Please enter an expression (or 'q') to quit:" SET /A Result=%Expr% if "%Expr%"=="" goto :eof if "%Expr%"=="q" goto :eof echo The result is %Result% goto repeat Here's a sample output from this script: Copy Code C:\Temp\Batchography>expr-calc.bat Please enter an expression (or 'q') to quit:1+2+3 The result is 6 Please enter an expression (or 'q') to quit:-5+2 The result is -3 Please enter an expression (or 'q') to quit:q C:\Temp\Batchography>

Trick #2 - Number counting

Number counting can come in handy in many scenarios. It could be used to create numbered dummy folders, write an algorithm that requires counting, or anything else you can think of. In Batch files, to count, you can use the following syntax: Copy Code FOR /L %variable IN (start, step, end) DO command [command-parameters] Let's illustrate by writing a small script that creates 10 dummy folders: "folder1" to "folder10": So let's assume we have the following files: Copy Code @echo off for /L %%a in (1, 1, 10) do (     mkdir folder%%a ) For example, let us write the sigma function (do the addition from 1 to N): Copy Code @echo off setlocal :repeat SET /P N="Please N for the sigma computation:" SET /A Result=0 for /L %%i in (1, 1, %N%) do ( SET /A Result=Result+%%i ) echo Sigma(%N%)=%Result%

Trick #3 - Reading a text file line by line in Batch files

Reading from a text file, one line at a time can be very handy for text scanning and transformation. Use the "FOR /F" syntax like this: Copy Code @echo off for /f "delims=" %%a in (the-file.txt) DO ( ECHO Line is: %%a ) We can implement a very basic non-empty lines counter in text files using the Batch language like this: Copy Code @echo off setlocal enabledelayedexpansion if "%1"=="" ( echo Please specify a file name! goto :eof ) set /A nlines=0 for /f "delims=" %%a in (%1) DO ( set /A nlines+=1 ) echo Total non-empty lines: %nlines% Quote: The "FOR /F" will skip empty lines in input text files.

Trick #4 - String substitution

With the Batch scripting language, it is possible to do string substitution. To achieve that, we have to have the string in question inside an environment variable. We can then use the following syntax: Copy Code %VarName:StringToBeMatched=StringToBeReplacedWith% Let's write an interactive script that replaces the word "books" with "ebooks" in the file "email.txt": Copy Code Reading books was the norm during his grandfather's time. People used to carry books with them to school in order to read and study. Some say that books will become obsolete one day. Who knows. The following script will do the string substitution and generate a new file called "new-email.txt": Copy Code @echo off setlocal enabledelayedexpansion if exist new-email.txt del new-email.txt for /f "delims=" %%a in (email.txt) DO ( set line=%%a echo !line:books=ebooks! >>new-email.txt ) Quote: We use the "setlocal enabledelayedexpansion" and the "!Var!" syntax instead of "%Var%" syntax when we are expanding variables from inside compound statements. The "new-email.txt" contents are: Copy Code Reading ebooks was the norm during his grandfather's time. People used to carry ebooks with them to school in order to read and study. Some say that ebooks will become obsolete one day. Who knows.

Trick #5 - Polyglot scripts: Mixing Python and Batch files

A very fun trick is when we can have a script that runs successfully under two or more languages. Let's illustrate how to do a mixed script for running Python and Batch files: Copy Code @echo off rem = """ :: From here and on, write any Batch file syntax and it will be ignored by Python :: :: The Batchography book by Elias Bachaalany :: python -x "%~f0" %* exit /b %errorlevel% :: End of batch file commands """ # Anything here is interpreted by Python import platform import sys print("Hello world from Python %s!\n" % platform.python_version()) print("The passed arguments are: %s" % sys.argv[1:]) The above is a Batch script that invokes itself again as a Python script using the "python -x" switch (skip the first line). Such scripts is useful when you first want a Batch script to set up or verify the right environment before invoking its embedded Python code.

Trick #6 - Polyglot scripts: A self-compiling C++ and Batch file

Similar to Trick #5, we can make a Batch file script that is also a C++ program. Here's how we can make a self-compiling C++ Batch script: Copy Code /* 2>nul && echo | set /p= @echo off setlocal echo. :: :: Self-compiling C++ Batch file! :: :: (c) Batchography book - by Elias Bachaalany :: set outfile="%~dpn0.exe" cl %~dpf0 /TP /EHsc /link /out:%outfile% /nologo > nul if exist %outfile% ( %outfile% del %outfile% del "%~dpn0.obj" ) else ( echo Compilation failed! ) goto :eof */ #include <stdio.h> int main() { printf( "Hello world! I was self-compiled!\n" "\n" "Checkout the Batchography book!\n"); return 0; } There's a slight drawback in this polyglot script. The first line ("/*" a C multi-line comment opening) is not a valid command and it will cause an error but we redirect the error to the nul device. We close the multi-line comment (with "*/") shortly after exiting the Batch file.

Trick #7 - Tokenizing commands output

It is possible to execute a command then break its output into tokens. To achieve this, we will use the "FOR /F" syntax. From the command prompt, to query the registry and retrieve the language ID, we can use the "reg.exe" command like such: Copy Code C:\Temp\Batchography>reg query "hklm\system\controlset001\control\nls\language" /v Installlanguage HKEY_LOCAL_MACHINE\system\controlset001\control\nls\language Installlanguage REG_SZ 0409 C:\Temp\Batchography> We have two lines output. We only care about the second line and the 3rd token (if we tokenize the string using the space character). To retrieve the 0409 value and write a Batch script to detect the system language, we can do the following: Copy Code @echo off for /F "usebackq tokens=3" %%a IN (`reg query "hklm\system\controlset001\control\nls\language" /v Installlanguage`) DO ( set lang_id=%%a ) :: 0409 English ; 0407 German ; 040C French ; 0C0A Spanish if "%lang_id%"=="0409" ( echo English detected ) else if "%lang_id%" == "040C" ( echo French detected ) else ( echo Note: Unknown language ID %lang_id%! ) The Batch script above, filters for lines that have at least 3 tokens and it captures the first token only.

Trick #8 - Functions are fun

Who said the Batch language is a simple language? Did you know that you can write functions (even recursive ones)? To define a function, simply create a label and then call it: Copy Code @echo off setlocal :main echo Hello! set /p a=Enter the first number: set /p b=Enter thne second number: call :add result %a% %b% echo The result is: %result% goto :eof :add <1=return var> <2=first number> <3=second number> echo Adding '%2' and '%3'. Returning the result into the variable '%1' set /a %1=%2+%3 goto :eof The function "add", is just a label and what comes after it in the same line are ignored / free text. As a convention, we put the argument number and its description. The "add" function is defined to take a return variable name (argument #1) and two numbers "a" (argument #2) and "b" (argument #3). The "main" function calls "add" using the "call :func_name" syntax and passing the arguments thereafter. When "add" returns, it may call "goto :eof" or "exit /b <return_code>". Many clever tricks are mentioned on my other pages, e.g. Solutions found on alt.msdos.batch. Every now and then a real "jewel" is sent to me by mail or otherwise. A selection of these tricks will be displayed on this page. Ever tried something like this? CMD /C "somecommand" "argument1" "argument2" It will return an error message like this one: 'somecommand" "argument1" "argument2' is not recognized as an internal or external command, operable program or batch file. Note the missing outer doublequotes in the error message. If the command line following CMD /C or CMD /K starts as well as ends with a doublequote, that entire command line is interpreted as a single (command) string. Jacques Bensimon ran into this quirk when trying to use a batch command in a context menu. After searching and experimenting for a long time, he came up with a brilliantly simple solution: don't let the first character be a doublequote, just insert an @ before the first doublequote (sorry, whitespace won't work): CMD /C @"somecommand" "argument1" "argument2" This works flawlessly, as expected. Based on Jacques' findings I started doing some experiments, and found the following alternative work-arounds: CMD /C ("somecommand" "argument1" "argument2") CMD /C ""somecommand" "argument1" "argument2"" The latter in particular surprised me... Or if you prefer to solve this as complicated as possible, use the short command name: FOR %%A IN ("somecommand") DO CMD /C %%~sA "argument1" "argument2" Thanks Jacques Another brilliantly simple way to check for elevated privileges, by Kevin Ridenhour: whoami /groups | find "12288" && echo Elevated or whoami /groups | find "12288" || echo Not Elevated Or I might add: WHOAMI /GROUPS | FIND "12288" >NUL SET /A Elevated = 1 - %ErrorLevel% The great advantage of this method compared to using OPENFILES is that the WHOAMI method will work in both 32-bit or 64-bit processes in 64-bit Windows. Thanks Kevin Aaron Thoma had a problem using Denis St-Pierre's elevation check. I'm not sure the problem could not have been solved (AT depends on a service that is often disabled; I often use OPENFILES instead to check for elevated privileges), but it did make him look for and find a way to not only check elevation level, but raise it as well: [The] trick for checking for elevation suddenly stopped to work for me: Microsoft Windows [Version 6.0.6002] Copyright (c) 2006 Microsoft Corporation. All rights reserved. Not enough storage is available to process this command. 14:38:03 C:\Windows\system32> echo %errorlevel% 0 14:38:08 C:\Windows\system32> at 14:38:10 C:\Windows\system32> echo %errorlevel% 1 So it yields the same errorlevel now as when run without Admin privileges. (I'm using Windows Vista Enterprise SP2. Maybe some update introduced a bug.) So I use another trick http://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights/4054937#4054937 now. Here is how I use it, in Evan's routine sites.google.com/site/eneerge/scripts/batchgotadmin that elevates a batch script itself: :: Ensure ADMIN Privileges :: adaptation of https://sites.google.com/site/eneerge/home/BatchGotAdmin and http://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights/4054937#4054937 @echo off :: Check for ADMIN Privileges >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" if '%errorlevel%' NEQ '0' (     REM Get ADMIN Privileges     echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"     echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"     "%temp%\getadmin.vbs"     del "%temp%\getadmin.vbs"     exit /B ) else (     REM Got ADMIN Privileges     pushd "%cd%"     cd /d "%~dp0"     @echo on ) Thanks Aaron Denis St-Pierre often shares the results of his research. This time, he sent me a couple of script snippets to check if the current script is running with administative (elevated) privileges: Sometimes you have to be running with admin privileges to get something done. With Windows 7 you have 2 security token when you login with admin. Noobs admins don't always right-click and run as administrator so you must check. Here are some code snippets to check inside the script. In VBS: Function IsADM     'detects if admin or Elevated     'Trick: AT > NUL returns %errorlevel% = 0 if you are Administrator/Elevated     Set WshShell = CreateObject( "WScript.Shell" )     Dim iErrNumber : iErrNumber = 0     Dim sErrDescription, sMsgText, iErrorCode     On Error Resume Next 'in case exe fails     iErrorCode = WshShell.Run( "CMD /C AT > NUL", 1, True ) 'True = Wait for completion.     iErrNumber = Err.number     sErrDescription = Err.Description     On Error Goto 0 'Turn error reporting back on (and clears Err object)     If iErrorCode = 0 Then         IsADM = True     Else         'WScript.Echo "you are NOT admin"         IsADM = False     End If End Function In CMD: @ECHO OFF ::Test If script has Admin Priviledges/is elevated AT > NUL IF %ERRORLEVEL% EQU 0 (     ECHO you are Administrator ) ELSE (     ECHO you are NOT Administrator. Exiting...     PING 127.0.0.1 > NUL 2>&1     EXIT /B 1 ) In PowerShell: $currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent( ) ) if ( -not ($currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ) ) ) {     Write-Error "This script must be executed in admin mode." -ErrorAction Stop } Thanks Denis Remember Brian Williams' big number math batch files? He is back with Add.bat, a batch file to add 2 positive integers of up to 2000 digits each (I did test that!). View the source or download it. Thanks Brian Remember Reinhard Irnberger's PUSHD trick to automatically map a drive if a UNC path is specified? I use PUSHD "%~dp0" ever since, as the first command in any batch file that could be run from a UNC path. Without this PUSHD command, the start/working directory of the batch file will be changed to %windir%\System32. Today Denis St-Pierre informed me that this change of working directory also occurs when a batch file is running in elevated mode in Windows Vista, Windows Server 2008 or Windows 7. I checked, and it occurs too when a script is started with RUNAS or PSEXEC. I suppose that's because my current/working directory is saved in my "volatile environment" in my profile, and thus is not available in the elevated user's environment. So there you have it, one more reason to specify (as opposed to assume) the working directory in your batch files. I think PUSHD "%~dp0" is by far the most reliable way to do this, and it will work with RUNAS, PSEXEC, UNC paths, UAC and Explorer's "Run as..." option. Thanks Reinhard and Denis A message by Steve Lessard: Doing basic string substitution as described on www.robvanderwoude.com/ntset.php was pretty easy when using constant values. But it took me a while to figure out how to do substitution when the string being substituted was determined programmatically. Now that I have it figured out I'd like to share it with you so that you might be able to share it with all of your readers... Prerequisite: Delayed expansion must be enabled Script: SETLOCAL ENABLEDELAYEDEXPANSION SET HOSTS_FILE=C:\Windows\System32\drivers\etc\hosts SET VARIABLE_HOSTS_FILE=!HOSTS_FILE:%SystemRoot%=%%SystemRoot%%! ECHO HOSTS_FILE=%HOSTS_FILE% ECHO VARIABLE_HOSTS_FILE=%VARIABLE_HOSTS_FILE% ENDLOCAL GOTO:EOF Output: HOSTS_FILE=C:\Windows\System32\drivers\etc\hosts VARIABLE_HOSTS_FILE=%SystemRoot%\System32\drivers\etc\hosts I used this kind of substitution to create machine independent instructions for fellow developers and testers to re-run my unit tests under their machine's source code location. Here's a sample of my script's output: ECHO To repeat this test run use the following command: pushd "%_LCSROOT%\dev\server\eps\CollaborationApi\utest\OotyTests\" & runall /run=x& popd Thanks Steve Ever tried SET /A to work with really large numbers? Then you may already know that numbers in batch math are severely limited in size. Brian Williams submitted 2 batch files that work with large numbers: IsLarger.cmd and Multiply.cmd: I want to submit these scripts/subroutines for the world to use, it allows one to multiply two numbers that are much larger than the limitations of SET /A. I don't know if I have plans to create more math related subroutines, but it seemed to be a necessity to do math in batch and not have to use some third party tool or another script language. The point of Multiply.cmd and IsLarger.cmd is to work with number beyond the command lines ability. You can Multiply an integer of hundreds of digits by another integer of many if not hundreds of digits. Also IsLarger.cmd can compare integers of thousands of digits long. The best part is this is all native NT Shell scripting tested on Windows XP, Windows 2003, and Windows Vista. (It might work on Windows 2000 - don't know haven't tried). No third party utilities are needed or reliance upon vbscript or some other language. I submitted these as batch files, but the subroutines starting with the labels :Multiply and :IsLarger is all you need to paste in your batch file to start using these - one other requirement, you have to have SETLOCAL ENABLEDELAYEDEXPANSION in your script for this to work. Syntax in your script: CALL :MULTIPLY CrazyLongNumber OtherCrazyLongNumber MyFavoriteVariable Do not surround your variables with percents or bangs (you are passing a reference to the variable, not the actual variable) CALL :MULTIPLY VariableContainingBigNumber OtherVariableConatiningBigNumber_GiveItToMeVariable or if you use my script as is its just MULTIPLY.CMD 121390487120394780398 123498712304981703948097 and you get the answer. Multiply.cmd 2 2 4 Same thing with IsLarger, prints TRUE, FALSE, or EQUAL (you are saying, is this first number larger than this second number?) IsLarger.CMD 2948372948273234234 2342983472938472938472342342342342234234 FALSE IsLarger.CMD 5 4 TRUE IsLarger.cmd 1 2 FALSE IsLarger.cmd 2 2 EQUAL If you use just the subroutine, no percents or bangs: CALL :IsLarger _BIGNUMBER1 _BIGNUMBER2 _MYVARIABLE I won't display the complete code here, the ZIPped sources for the batch files can be downloaded here. I do want to show you the main trick that is used in both batch files: FOR /L %%B IN (0,1,9) DO SET _NUMVAR=!_NUMVAR:%%B=%%B ! Note the space in the part of the code that was marked red: it means a space will be inserted after each digit. Next each digit is multiplied or compared separately in a "regular" FOR loop. Brilliant! Thanks Brian   Leonardo Gutierrez Ramirez found a way to use numbers as variables in a FOR loop: Hi, I have discovered that we can use numbers in the variable FOR command, like this: FOR /L %%ˆ6 IN (1 1 10) DO (     ECHO %%ˆ6 ) FOR /D /R %%ˆ2 IN (*) DO (     ECHO %%ˆ2 ) FOR /F "tokens=1,2,3" %%ˆ0 IN ('VER') DO (     ECHO.%%ˆ0 %%ˆ1 %%ˆ2 ) FOR %%ˆ1 IN ("%~nx0") DO (     ECHO %%~nxtˆ1 ) FOR /F "tokens=1" %%ˆ7 IN ('VER') DO (     ECHO.%%ˆ7 ) FOR %%ˆ1 IN ("%~nx0") DO (     ECHO %%~nxatdˆ1 ) Combined with Carlos M.'s extension of the available variables in FOR loops, we can now, in theory at least, nest up to 75 FOR loops! Not that I would want to maintain such code... Thanks Leo Leonardo Gutierrez Ramirez found a way to add any (language) style comments to SET /P commands: @echo off set /p "suma=2+2 : " # La suma, my comment if %suma% equ 4 (     echo.Bien ) else (     echo.Mal ) set suma @echo off set /p "suma=2+2 : "  // La suma if %suma% equ 4 (     echo.Bien ) else (     echo.Mal ) set suma @echo off set /p "suma=2+2 : "  /* La suma */ if %suma% equ 4 (     echo.Bien ) else (     echo.Mal ) set suma @echo off set /p "suma=2+2 : "  ' Comment if %suma% equ 4 (     echo.Bien ) else (     echo.Mal ) set suma @echo off set /p "suma=2+2 : "  Comment if %suma% equ 4 (     echo.Bien ) else (     echo.Mal ) set suma @echo off for %%. in (H e l l o _ W o r l d) do (     call :show %%. ) goto:eof :show <nul set /p "=%*" // Comentario ping -n 1 loopback > nul goto:eof As a matter of fact, any text after the closing doublequote is ignored. Thanks Leo Carlos M. found a way to increase the number of variables available in a FOR /F loop (I added some highlighting to discriminate between the individual lines): I just discovered that you can use more than 26 characters in the command for /f, so we can reach a limit of 31 tokens. In order for /f "tokens=1" or simply for /f can use the following characters: & : < We can start from and arrive at characters within this range from low to high: > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ˆ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } Note: The following characters must be escaped with a caret (ˆ): ˆ < > | & as follows: ˆˆ ˆ< ˆ> ˆ| ˆ& Finally a few examples: @echo off set palabras=p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 p16 p17 p18 p19 p20 p21 p22 p23 p24 p25 p26 p27 p28 p29 p30 p31 p32 p33 p34 p35 for /f "tokens=1" %%ˆ& in ("%palabras%") do (echo %%ˆ&) for /f "tokens=1" %%ˆ< in ("%palabras%") do (echo %%ˆ<) for /f "tokens=1,2,3" %%ˆ> in ("%palabras%") do (echo %%ˆ>%%?%%@) for /f "tokens=1-31" %%? in ("%palabras%") do (echo %%?%%@%%A%%B%%C%%D%%E%%F%%G%%H%%I%%J%%K%%L%%M%%N%%O%%P%%Q%%R%%S%%T%%U%%V%%W%%X%%Y%%Z%%[%%\%%]) for /f "tokens=1-31" %%ˆˆ in ("%palabras%") do (echo %%ˆˆ%%_%%`%%a%%b%%c%%d%%e%%f%%g%%h%%i%%j%%k%%l%%m%%n%%o%%p%%q%%r%%s%%t%%u%%v%%w%%x%%y%%z%%{%%ˆ|) for /f "tokens=1-31" %%_ in ("%palabras%") do (echo %%_%%`%%a%%b%%c%%d%%e%%f%%g%%h%%i%%j%%k%%l%%m%%n%%o%%p%%q%%r%%s%%t%%u%%v%%w%%x%%y%%z%%{%%ˆ|%%}) pause updated The value of my discovery is that for example if, before declaring: for /f "tokens=1-4" %%Y in ("p1 p2 p3 p4") do (echo %%Y%%Z...) and wanted to show the token 1,2,3,4 we could only show the token 1 and 2, because then comes %%Y and %%Z then ... we did not know that letter came. So now we can do: for /f "tokens=1-4" %%Y in ("p1 p2 p3 p4") do (echo %%Y%%Z%%[%%\) The other, the documents said 52 variables, ie 26 + 26 (az) and (AZ) for an inside another for, or for using a single letter of the alphabet any case, but now we have more than 65 variables, for example: @echo off for /f "tokens=1-10" %%ˆ> in ("El cmd.exe es el interprete de comandos en OS2 y") do (     for /f "tokens=1-10" %%H in ("sistemas basados en Windows NT incluyendo Windows 2000 Windows XP") do (         for /f "tokens=1-8" %%R in ("Windows Server 2003 y Windows Vista. Es el") do (             for /f "tokens=1-12" %%Z in ("equivalente de command.com en MS-DOS y sistemas de la familia Windows 9x.") do (                 for /f "tokens=1-10" %%f in ("A diferencia de su antecesor command.com este programa es tan") do (                     for /f "tokens=1-14" %%p in ("solo una aplicacion no es una parte del sistema operativo y no posee la") do (                         echo %%ˆ>%%?%%@%%A%%B%%C%%D%%E%%F%%G%%H%%I%%J%%K%%L%%M%%N%%O%%P%%Q%%R%%S%%T%%U%%V%%W%%X%%Y%%Z%%[%%\%%]%%ˆˆ%%_%%`%%a%%b%%c%%d%%e%%f%%g%%h%%i%%j%%k%%l%%m%%n%%o%%p%%q%%r%%s%%t%%u%%v%%w%%x%%y%%z%%{%%ˆ|%%}                     )                 )             )         )     ) ) pause So, in summary, my discovery increases the maximum of 26 to 31 tokens, and the maximum for the global variables, from 52 to over 65, more say in the example just because they are used within the range, but not individual characters. Written by Carlos M. Wow! I need a wider screen. Thanks Carlos Nushu sent me a message with a nice technique to combine multiple scripting languages in a single script: The purpose is to be able to execute AutoIT code directly from inside a batch file (and so not having to worry about .au3 being associated with autoit). .CMD file begins : ------------------------------------------- ; ::Batch code ,cmd.exe will run the lines beginning with comma and exit ; @echo off ; start C:\AutoIT\AutoIT3.exe %0 %* ; exit ; :: AutoIT code , autoit will ignore the lines beginning with semicolon MsgBox (0 , "Box" , "Just A Box") ------------------------------------------- .CMD file ends Nice trick, and it can be used for KiXtart scripts too! Try the following code, saved as batch file; if KiXtart is available (and KIX32.EXE is in the PATH) it will display the current date and time in YYYYMMDDHHmmss format: ; @ECHO OFF ; KIX32.EXE "%~f0" ; EXIT $RC = SetOption( "ASCII", "ON" ) SubStr( @Date, 1, 4 ) SubStr( @Date, 6, 2 ) SubStr( @Date, 9, 2 ) SubStr( @Time, 1, 2 ) SubStr( @Time, 4, 2 ) SubStr( @Time, 7, 2 ) It can even be used in .REG files! Thanks Nushu One never stops learning, as this new trick by Justin proves: Recently I came across a little trick using the REN command (tested on XP). It is possible to use REN to delete every character after the last occurrence of a character by using the wildcard * followed by the desired character: REM Rename filename.ext to filena REN filename.ext *a If you wanted to use a space or ampersand it would need to be contained in double quotes i.e. REN "good&bad" "*&" If you use a dot "*." the dot will also be trimmed off by windows because it doesn't allow file names to end with a dot. Very simple but seemingly undocumented............... Marvellous! Thanks Justin I must confess, this tip by Leo Gutierrez Ramirez baffled me. I didn't expect it to work, but it does. See for yourself: @ECHO OFF REM Code: ECHO Hello REM Comments: (COMMENT HELLO! HI! The trick is not in the exclamation marks, it is the fact that the parenthesis opens a "code block" which is never closed, and thus seems to be ignored completely by the command interpreter. As Leo said: a useful way to add comments at the end of a batch file. Thanks Leo "typewriter" batch file: :: Copyright (c) 2008 Carlos M. :: All rights reserved. :: :: Redistribution and use in source and binary forms, with or without :: modification, are permitted provided that the following conditions :: are met: :: 1. Redistributions of source code must retain the above copyright :: notice, this list of conditions and the following disclaimer. :: 2. Redistributions in binary form must reproduce the above copyright :: notice, this list of conditions and the following disclaimer in the :: documentation and/or other materials provided with the distribution. :: 3. Neither the name of copyright holders nor the names of its :: contributors may be used to endorse or promote products derived :: from this software without specific prior written permission. :: :: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS :: ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED :: TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR :: PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS :: BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR :: CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCU::ENT OF :: SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS :: INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN :: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) :: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE :: POSSIBILITY OF SUCH DAMAGE. :: @ECHO OFF CALL :Put Me canse de escribir tantas lineas ... CALL :Put Este es el typewritter effect reloaded CALL :Put o echo dinamico xD >NUL PAUSE GOTO:EOF :Put IF NOT DEFINED .m_ SET.m_=%* IF NOT DEFINED .m_ GOTO:EOF <NUL SET /P .m_=%.m_:˜0,1% >NUL PING -n 10 SET .m_=%.m_:˜1% IF DEFINED .m_ (GOTO:Put) ELSE (ECHO.) GOTO:EOF Try it out, and see if you understand how it works. Thanks Carlos Justin wrote: I was looking at you tips and tricks section and noticed you have a way to get the first and last line of a text file, but what it you want the /nth /line of text... This trick takes advantage of two features of NT batch, calling batch labels and GOTO :EOF, which of course ends a call(or batch). The folowing will get the third line of a text file and set it to the variable TEXT_LINE. SET NthLine=3 SET /A NthLine-=1 CALL :LINEGRAB GOTO RESTOFFILE :LINEGRAB FOR /F "skip=%NthLine% tokens=*" %%G IN (TEXTFILE.TXT) DO SET TEXT_LINE=%%G&&GOTO :EOF :RESTOFFILE There is one limitation to this, because for /f skips blank lines any blank lines will cause the line grabbed to be off. Thanks Justin. While adding this tip to this web page, the following idea came up (combining this tip with John Taylor's): SET /A NthLine -= 1 TYPE textfile ˆ| MORE /E +%NthLine% > "%Temp%.\~nthline.tmp" SET /P TextLine= < "%Temp%.\~nthline.tmp" This will also set the value of variable TextLine to the content of the nth line of textfile. A big advantage is MORE doesn't ignore empty lines. Unfortunately it involves the use of a temporary file. But how about this: SET NthLine=3 SET /A NthLine -= 1 CALL :LINEGRAB GOTO RESTOFFILE :LINEGRAB FOR /F "tokens=*" %%A IN ('MORE /E +%NthLine% TEXTFILE.TXT') DO (     SET TextLine=%%A     GOTO:EOF ) :RESTOFFILE It doesn't use a temporary file. Any disadvantages, anyone...? Jeffrey Ehrhart wrote to me: • For the DIR command, adding the /S flag to the /B will fully qualify file and directory names. Of course, using the /S flag will search all subdirectories which may not be what you want. Examples: DIR /B /S C:\Temp C:\Temp\A.txt C:\Temp\B.txt DIR /B C:\Temp A.txt B.txt • If you add the /S flag when deleting a specific file, you will get a resulting message with the file name fully qualified. Again, use the /S flag with caution on a delete. Without the /S flag, you do not get a message. Example: C:\Temp>DEL /S A.txt Deleted file - C:\Temp\A.txt C:\Temp> Thanks Jeffrey, I'll certainly keep that in mind. But, as Jeffrey states, it is risky, unless of course there are no subdirectories involved. A message from Matthew W. Helton: Rob, I feel your pain about finding a logged-on user... but there is hope from the most unlikely of places... using Netsh. netsh diag show computer /v Checkout near the bottom: UserName = If there is no username, there is no logged-on user. Sweet! Programmatically in Batch: FOR /F "tokens=3" %%a IN ('netsh diag show computer /v ˆ| FIND /i "username"') DO ECHO %%a Remotely using Batch via Psexec: SET REMOTE_COMPUTER=%1 FOR /F "tokens=3" %%a IN ('PSEXEC.exe \\%REMOTE_COMPUTER% netsh diag show computer /v ˆ| FIND /i "username"') DO ECHO %%a Note: this only gets you the locally logged-on user: Windows Server 2003 Terminal Services Users will not show up on this. Great, thanks Matthew. In theory, using NETSH's own -r \\%REMOTE_COMPUTER% switch should do the trick without the need for PSEXEC, but my own tests using this switch failed. PSEXEC will do the trick. Experiment with the output of the NETSH Diag Show Computer /V command, there is a lot of valuable information available. A beautiful feature discovered by John Taylor: how to use SET /P to read the first line of a file and store it in an environment variable. Try these commands, and notice the differences: SET /P TestVar=<C:\boot.ini SET TestVar FOR /F "tokens=*" %%A IN (C:\boot.ini) DO SET TestVar=%%A SET TestVar You see? SET /P reads the first line, whereas FOR /F reads the last line! Thanks, John. A great feature discovered by Reinhard Irnberger: Sometimes it is necessary to get a Driveletter from a mapping. Normally you will define a fixed Driveletter which you will map to your Remote path. After you have done all things you will delete the drive mapping. I found an simple solution for Windows Server 2003. Maybe it's also working in Windows 2000. With PUSHD \\Server\Share\[path] the system automatically creates a driveletter for you and jumps to it. In the next step you can get the current Drive with %CD%. When you use POPD you will jump back and the drivemapping is also deleted. Attention: Because PUSHD can be nested you have to make sure that with POPD you don't delete your current directory! In my scripts I will use the following lines to Map a remote path: PUSHD \\Server\Share\path SET rmt=%CD% CALL %rmt%\script.cmd Wow! Great! This works in Windows NT 4 (SP6a), 2000 and XP (Professional) too. Thanks, Reinhard. Update: Daniel Schmidt reported that this trick does not work in scheduled tasks in Windows 2000 Server. I tried it on my Windows XP machine, and it did work, so this may be a Windows 2000 issue (bug). Always test before implementing any script. Daniel Schmidt also sent me this link to a Microsoft KnowledgeBase article documenting this trick (for Windows 2000 Server!). Thanks, Daniel. Here is a simple batch file to test if the PUSHD trick works in scheduled tasks on your computer: PUSHD \\server\sharename CD >> C:\pushdtest.log POPD CD >> C:\pushdtest.log Run it twice, first from the command line, and then scheduled with your own credentials. C:\pushdtest.log should contain 4 (when run twice) lines like these: X:\ C:\ X:\ C:\ A nice tip by Chris Greenbank: Under XP (And I assume NT/2000 as well) it is possible to create a group of commands from the command line (not a batch file). This is useful for, among other things, copy&pasting scripts off of the internet for testing without saving. To do this, just type "(" and hit enter, enter each command one by one (or paste a previously copied list of commands), then type ")" and hit enter.   As well, it allows for groups of commands intended to be executed sequentially to be entered beforehand then allowed to run, useful if you are running external programs that you need to wait for and don't want to write a batch file for that specific job.   Output for every command may also be redirected at the end of the block, in the same manner as Tip 7 on the Clever Tricks page: ") > log.txt" instead of just ")"   C:\>( More? cd More? echo %windir% More? echo This may be useful to someone wanting More? echo to enter multiple commands in a row... More? )   C:\ C:\WINDOWS This may be useful to someone wanting to enter multiple commands in a row... C:\> Update: Rev. David Isakson pointed out that [a] this works in Windows 8 as well (and I can add to that: in Windows 7 too), and [b] instead of writing to a log file, one can also write the commands to a batch file, using ECHO before each command. I experimented a little and found out the latter can be used to "nest" the trick, allowing to create as well as run a batch file from the command line: C:\>( More? ( More? echo @echo off More? echo echo This may be useful to someone wanting More? echo echo to enter multiple commands in a row... More? ) > test.bat More? test.bat More? ) This may be useful to someone wanting to enter multiple commands in a row... C:\> Not quite as convenient as Notepad, but it just might be useful some day — and it is fun to investigate. Thanks, Chris, and thanks Rev. Isakson. And another tip by Chris Greenbank, about escaping linefeeds: Well, now I just found something more useful: A way to increase readability.   After thinking about why this works under NT/etc, I realized its because under windows, a physical linebreak is represented by 2 characters, and the escape character only escapes one of them, leaving the other there. My guess is that under OS/2, a linebreak is only a single character.   After some testing, I found that using just the second half of the linebreak, its possible to make it all neat and non-spaced out (the hex value is shown in notepad as a square, it won't provide a physical linebreak):   ECHO testˆ<hex 0A> testˆ<0A> testˆ<0A> test   will result in:   test test test test   To use in a SET command:   SET var=testˆˆˆ<0A><0A>ˆ<0A> testˆˆˆ<0A><0A>ˆ<0A> testˆˆˆ<0A><0A>ˆ<0A> test   ECHO %var%   will result in:   test test test test   The reason that the <0A> is repeated twice is that the first one is ignored, which would be interpreted as ˆˆˆˆ which is not what is wanted. Having a few control codes on the end instead of excessive linebreaks is much more readable, while providing the same behaviour. The only requirement is that you be able to enter the correct value. Thanks, Chris. Martin Richards wrote about IBM's E-Gatherer tool: I have [...] discovered that you can run 'egather2 -local' to output the native XML file rather than the .eg2 file.   You can also specify which information is output by egather2 by specify the probe name on the command line, eg: egather2 -local -probe SYSTEM_SUMMARY You can get a list of probe names to specify by running egather2 -listprobes Well, this sure is a nice simple replacement for my (now obsolete) SNDisk2.bat.   Thanks, Martin   Continuing Martin's research I have discovered even more undocumented command line switches. I used the command: STRINGS -a EGATHER2.EXE | FINDSTR /R /B /I /C:"-[A-Z0-9][A-Z0-9]" to find the following possible switches: -Y2 -ht -wB -binaryfile -xmlfile -textfile -asciifile -dgmlfile -vpd -stdout -local -probe -probes -listprobes -html -filename -level -debug -step -nolimit -silent -batch -help -64OS -zc Try the -help switch and you'll notice that most of these switches are indeed undocumented. Experiment with these switches, you may find some are of great value. My favorite so far: EGATHER2 -html -batch which I used in my new SNDisk3.bat. Or add this one to your login script: EGATHER2 -html -batch -filename\\remoteserver\remoteshare\%ComputerName% (no space between -filename and the actual file name; and no extension, the -html switch will take care of that) Note: SNDisk3.bat has become obsolete again, as IBM keeps changing the inner workings of EGatherer. Try SNDisk4.bat with EGatherer 2.51.*, which produces a large XML file with the results of a full inventory. From Fred Langa and several other sources came this tip to add a Compatibility Mode tab to all shortcuts' properties in Windows 2000 (SP2 or later). Run the following command only once: REGSVR32 %systemroot%\apppatch\slayerui.dll From that moment on you can choose to run any program in Windows 95 or NT 4 compatibility mode. James Higgs sent me a nice PAUSE replacement using Windows 2000's SET /P: ECHO Press Enter to continue . . . SET /P = Unlike PAUSE, which accepts any key, SET /P will only accept the Enter key.   Josh Murray sent me an improvement, a one-liner with the same functionality: SET /P =Press Enter to continue . . . Gabor Funk showed me this undocumented feature of the DIR command: DIR, which is shorthand for: DIR /A Works only in COMMAND.COM (tested in Windows 2000). An ingenious way to use CHOICE is demonstrated by Laurence Soucy's version(s) of BootDriv.bat. :: bootdrv1.bat @ECHO off :: By Laurence Soucy :: http://web.archive.org/web/20091104082534/http://users.telenor.dk/~dsl645578/batfiles.htm:: :: To place drive letter into variable ECHO %comspec%|choice.com/n/c%comspec% set bootdrv=>%temp%.\bootdrv$.bat FOR %%c in (CALL DEL) do %%c %temp%.\bootdrv$.bat ECHO "%bootdrv%" The same trick could be used to determine the current drive letter: @ECHO OFF CD | CHOICE /N /C:ABCDEFGHIJKLMNOPQRSTUVWXYZ SET curdrive=>%temp%.\curdrv$.bat FOR %%A IN (CALL DEL) DO %%A %temp%.\curdrv$.bat ECHO "%curdrive%" Tip (and the bootdrv* batch files themselves) provided by Laurence Soucy   Group commands for redirection:   To log the result of several commands, a commonly used method is command1logfile.log command2 >> logfile.log command3 >> logfile.log In Windows NT4/2000/XP command grouping can be used to simplify the code: ( command1 command2 command3 ) > logfile.log Tip provided by Dave Denholm Use VER to prevent NET USE asking for a password in NT: VER | NET USE * \\server\share [ /USER:domain\user ] Note: this will skip the drive mapping and display an error message if the user ID/password combination is invalid (in other words: if you don't have the rights you just won't get the drive mapping)   This trick works because VER's output starts with an empty line, which — when redirected like this — has the same result as pressing the Enter key   Tip provided by Justin F. Kuo Check if a server is available:   A well known way to check if a server is stil "on the air" is PING server | FIND "TTL=" >NUL IF ERRORLEVEL 1 ECHO Error pinging server In Windows 95/98/NT this will result in 4 tries from PING to detect the presence of server. When checking large amounts of servers, some time could be saved by a fast check, followed by a more thorough check only when the first check fails. PING server -n 1 | FIND "TTL=" >NUL IF NOT ERRORLEVEL 1 GOTO Next PING server -w 3000 | FIND "TTL=" >NUL IF ERRORLEVEL 1 ECHO Error pinging server :Next Tip provided by Mark Johnson, and improved (using "TTL=" instead of "TTL") by Richard Parvass Swap your mouse to left handed use: Windows 95: RUNDLL USER.EXE,SwapMouseButton Windows NT: RUNDLL32 USER32.DLL,SwapMouseButton Tip provided by "Speedy Gonzales"   I know of no command yet to undo this, but the following will get you close: CONTROL MAIN.CPL or: RUNDLL32 SHELL32.DLL,Control_RunDLL MAIN.CPL,@0,1 Start DialUp Network: START RUNDLL32 RNAUI.DLL,RnaDial exact name of dialer entry TRACERT -h 1 -w 1 The RUNDLL command starts DUN, the TRACERT command is supposed to actually start the dialing process. Since I do not have access to any PC with DUN installed, I could not test the TRACERT command's effect.   Tip provided by Michael J. Gregg and Tom Lavedas on alt.msdos.batch Resolve host name for specified IP address (Windows NT): FOR /F "tokens=2 delims= " %%A IN ('PING -a %1 ˆ| FIND "[%1]"') DO ECHO.%%A The HostName.bat examples use this trick.   Adapt the FIND /V part if you expect host names containing the (sub)string "TTL".   Tip provided by Marcel van der Wal Enclose DEBUG scripts in batch file: DEBUG < %0.BAT GOTO Around (do not skip this blank line) (original DEBUG script goes here) (do not skip this blank line) :Around Technique first seen at McAfee's site Check if a scheduled event job is active or not:   Oskar Bäckström sent me this great tip: The win98 tasks scheduler creates .JOB files when you setup a program to run on a certain occasion. These .JOB files contain information about some things like the path to the program that should be run and probably when it will run. I've also seen that there's allways a character that indicates whether the .JOB file is active or not. Checking if a certain task will be run or not could then be done in a batch file: TYPE C:\Windows\Tasks\Thejobb.job | FIND "%character%" >NUL IF NOT ERRORLEVEL 1 ECHO Job not active! IF ERRORLEVEL 1 ECHO Job is active! Where %character% is the one character that will disappear from the .JOB file when it's activated. I've found that the ASCII 196 is a pretty safe bet, but you should scan the file you want to test, both when you have it activated and when it is inactive. Then check if it's really this character that differs (I suggest looking at it in notepad, thats how I did it). If you're really unlucky the active-or-not charcter could be found twice in the file, on its active-or-not position and some other place, this test will then not work. Check also that your program's name does not contain the control character. Tip provided by Oskar Bäckström Uncommon use of NT's SET /A switch: As you may or may not be aware, it isn't necessary to specify a variable when using SET /A. Try this: SET /A 0XFF and you should see the number 255 (the decimal value of the hexadecimal number FF) on screen. Used without a variable, the result of the mathematical expression is displayed on screen. As explained on my "Useless Tips" page in more detail, the result is displayed without a carriage return/line feed at the end!  

GOTO :EOF

to exit a subroutine :EOF is a predefined label. The help goto /? explains also this special label for End Of File. The help call /? explain both that goto :EOF should be used to exit a subroutine called with call :Label. goto :EOF as well as exit /B can be both used everywhere to either exit a subroutine or exit the current batch file processing. Note 1: goto EOF without a colon requires that there is really a line starting with :EOF in the batch file, i.e. the label EOF must exist in the file. goto :EOF always results in exiting subroutine/batch processing with command extensions enabled even if there is a label EOF in the batch file because of a line starting with :EOF. Note 2: Command EXIT without parameter /B results always in exiting entire command process independent on calling hierarchy and independent on how the Windows command processor was started – with parameter /K to keep cmd.exe running as used when opening a command prompt window or with /C to close after command processing finished as used on double clicking a batch file. Therefore exit without /B should be used wisely in a batch file (best: never). Note 3: exit /B does not work with command extensions disabled as demonstrated by this code: @echo off setlocal DisableExtensions echo Use command exit /B with command extensions disabled. exit /B Executing this batch file from within a command prompt window results in output of the error message: The system cannot find the batch label specified - EOF In other words exit /B without an additional exit code is exactly like goto :EOF and depends therefore also on command extensions. exit without /B without or with an exit code works always. Note 4: ERRORLEVEL is not affected by goto :EOF, but the Microsoft GOTO documentation is mute on this topic. exit /B # sets ERRORLEVEL to # as documented by Microsoft. exit /B # can be also used instead of goto :EOF to exit a subroutine with a specific exit code evaluated on the command line calling the subroutine like on using the operators && or || or on next command after calling command line with if errorlevel X. However, explicitly exiting a batch file or a subroutine with a specific exit code is usually not needed as neither goto :EOF nor exit /B modify the current value of ERRORLEVEL. Note 5: Do not use goto:EOF or call:Label in a batch file with no space between command GOTO respectively CALL (argument 0) and the label (argument 1). There should be always used goto :EOF and call :Label with a space as argument string separator between command and label. The reason is that goto:EOF results in the attempts to find in current directory first a file with name goto: and next a file with name goto:EOF. The incorrect command call:Label results in searching for a file with name call: and next with name call:Label. The file system returns for both syntactically wrong commands twice to cmd.exe that the name is invalid. Then cmd.exe detects the colon as reason for the invalid name and splits the command up into command and label argument and finally runs the command with success. The usage of goto :EOF and call :Label does not cause any wrong file system accesses as cmd.exe immediately recognizes the string goto respectively call as internal command.

Read file contents into a variable

for /f "delims=" %%x in (version.txt) do set Build=%%x or set /p Build=Reading a text file line by line and storing it in an array @echo off set "file=C:\Users\Admin\Documents\url.txt" set /A i=0 for /F "usebackq delims=" %%a in ("%file%") do ( set /A i+=1 call echo %%i%% call set array[%%i%%]=%%a call set n=%%i%% ) for /L %%i in (1,1,%n%) do call echo %%array[%%i]%% another method: @echo off &setlocal enabledelayedexpansion for /F "delims=" %%a in (C:\Users\Admin\Documents\url.txt) do ( set /A count+=1 set "array[!count!]=%%a" ) for /L %%i in (1,1,%count%) do echo !array[%%i]! Inside a code block you need delayed expansion and !variables!. another method: @ECHO OFF SETLOCAL FOR /f "tokens=1*delims=:" %%i IN ('findstr /n /r "$" url.txt') DO SET max=%%i&SET array[%%i]=%%j FOR /l %%i IN (1,1,%max%) DO CALL ECHO(%%array[%%i]%% GOTO :EOF provided no line begins ":"

Arrays

Arrays are not specifically defined as a type in Batch Script but can be implemented. The following things need to be noted when arrays are implemented in Batch Script. Each element of the array needs to be defined with the set command. The ‘for’ loop would be required to iterate through the values of the array.

Creating an Array

An array is created by using the following set command. set a[0]=1 Where 0 is the index of the array and 1 is the value assigned to the first element of the array. Another way to implement arrays is to define a list of values and iterate through the list of values. The following example show how this can be implemented. Example @echo off set list=1 2 3 4 (for %%a in (%list%) do ( echo %%a )) Output The above command produces the following output. 1 2 3 4

Accessing Arrays

You can retrieve a value from the array by using subscript syntax, passing the index of the value you want to retrieve within square brackets immediately after the name of the array. Example @echo off set a[0]=1 echo %a[0]% In this example, the index starts from 0 which means the first element can be accessed using index as 0, the second element can be accessed using index as 1 and so on. Let's check the following example to create, initialize and access arrays − @echo off set a[0]=1 set a[1]=2 set a[2]=3 echo The first element of the array is %a[0]% echo The second element of the array is %a[1]% echo The third element of the array is %a[2]% The above command produces the following output. The first element of the array is 1 The second element of the array is 2 The third element of the array is 3

Modifying an Array

To add an element to the end of the array, you can use the set element along with the last index of the array element. Example @echo off set a[0]=1 set a[1]=2 set a[2]=3 Rem Adding an element at the end of an array Set a[3]=4 echo The last element of the array is %a[3]% The above command produces the following output. The last element of the array is 4 You can modify an existing element of an Array by assigning a new value at a given index as shown in the following example − @echo off set a[0]=1 set a[1]=2 set a[2]=3 Rem Setting the new value for the second element of the array Set a[1]=5 echo The new value of the second element of the array is %a[1]% The above command produces the following output. The new value of the second element of the array is 5

Iterating Over an Array

Iterating over an array is achieved by using the ‘for’ loop and going through each element of the array. The following example shows a simple way that an array can be implemented. @echo off setlocal enabledelayedexpansion set topic[0]=comments set topic[1]=variables set topic[2]=Arrays set topic[3]=Decision making set topic[4]=Time and date set topic[5]=Operators for /l %%n in (0,1,5) do ( echo !topic[%%n]! ) Following things need to be noted about the above program − Each element of the array needs to be specifically defined using the set command. The ‘for’ loop with the /L parameter for moving through ranges is used to iterate through the array. Output The above command produces the following output. Comments variables Arrays Decision making Time and date Operators

Length of an Array

The length of an array is done by iterating over the list of values in the array since there is no direct function to determine the number of elements in an array. @echo off set Arr[0]=1 set Arr[1]=2 set Arr[2]=3 set Arr[3]=4 set "x = 0" :SymLoop if defined Arr[%x%] ( call echo %%Arr[%x%]%% set /a "x+=1" GOTO :SymLoop ) echo "The length of the array is" %x% Output Output The above command produces the following output. 1 2 3 4 "The length of the array is" 4

Creating Structures in Arrays

Structures can also be implemented in batch files using a little bit of an extra coding for implementation. The following example shows how this can be achieved. Example @echo off set obj[0].Name=Joe set obj[0].ID=1 set obj[1].Name=Mark set obj[1].ID=2 set obj[2].Name=Mohan set obj[2].ID=3 FOR /L %%i IN (0 1 2) DO ( call echo Name = %%obj[%%i].Name%% call echo Value = %%obj[%%i].ID%% ) The following key things need to be noted about the above code. Each variable defined using the set command has 2 values associated with each index of the array. The variable i is set to 0 so that we can loop through the structure will the length of the array which is 3. We always check for the condition on whether the value of i is equal to the value of len and if not, we loop through the code. We are able to access each element of the structure using the obj[%i%] notation. Output The above command produces the following output. Name=Joe Value=1 Name=Mark Value=2 Name=Mohan Value=3

Create list or arrays

declare a list or array you may use both ways. If you just want to separate the elements and show them in separated lines, a list is simpler: set list=A B C D A list of values separated by space may be easily processed by for command: (for %%a in (%list%) do ( echo %%a echo/ )) > theFile.txt You may also create an array this way: setlocal EnableDelayedExpansion set n=0 for %%a in (A B C D) do ( set vector[!n!]=%%a set /A n+=1 ) and show the array elements this way: (for /L %%i in (0,1,3) do ( echo !vector[%%i]! echo/ )) > theFile.txt For further details about array management in Batch files, see: Arrays, linked lists and other data structures in cmd.exe (batch) script ATTENTION! You must know that all characters included in set command are inserted in the variable name (at left of equal sign), or in the variable value. For example, this command: set list = "A B C D" create a variable called list (list-space) with the value "A B C D" (space, quote, A, etc). For this reason, it is a good idea to never insert spaces in set commands. If you need to enclose the value in quotes, you must enclose both the variable name and its value: set "list=A B C D" PS - You should NOT use ECHO. in order to left blank lines! An alternative is ECHO/. For further details about this point, see: http://www.dostips.com/forum/viewtopic.php?f=3&t=774

parse markup language

The batch language isn't terribly well-suited to parse markup language like HTML, XML, JSON, etc. In such cases, it can be extremely helpful to use a hybrid script and borrow from JScript or PowerShell methods to scrape the data you need. Here's an example. Save it with a .bat extension and give it a run. @if (@CodeSection == @Batch) @then @echo off & setlocal set "url=http://www.domain.com/data/page/1" for /f "delims=" %%I in ('cscript /nologo /e:JScript "%~f0" "%url%"') do ( rem // do something useful with %%I echo Link found: %%I ) goto :EOF @end // end batch / begin JScript hybrid code // returns a DOM root object function fetch(url) { var XHR = WSH.CreateObject("Microsoft.XMLHTTP"), DOM = WSH.CreateObject('htmlfile'); XHR.open("GET",url,true); XHR.setRequestHeader('User-Agent','XMLHTTP/1.0'); XHR.send(''); while (XHR.readyState!=4) {WSH.Sleep(25)}; DOM.write('<meta http-equiv="x-ua-compatible" content="IE=9" />'); DOM.write(XHR.responseText); return DOM; } var DOM = fetch(WSH.Arguments(0)), links = DOM.getElementsByTagName('a'); for (var i in links) if (links[i].href && /\/post\/view\//i.test(links[i].href)) WSH.Echo(links[i].href);

Regex to match a variable in Batch scripting

findstr has no full REGEX Support. Especially no {Count}. You have to use a workaround: echo %var%|findstr /r "^[a-z][a-z]$ ^[a-z][a-z][a-z]$" which searches for ^[a-z][a-z]$ OR ^[a-z][a-z][a-z]$ (Note: there is no space between %var% and | - it would be part of the string) or use cscript